package com.small.nacos.core.code;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
import com.alibaba.nacos.common.utils.ArrayUtils;
import com.small.nacos.sys.env.EnvUtil;
import org.reflections.Reflections;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static com.small.nacos.sys.env.Constants.REQUEST_PATH_SEPARATOR;

/**
 * @Author zhoujin
 * @Date 2022/10/31 15:57
 */
@Component
public class ControllerMethodsCache {

    private Map<String, Method> methods = new ConcurrentHashMap<>();


    public Method getMethod(HttpServletRequest request) {
        String path = getPath(request);
        String httpMethod = request.getMethod();
        String urlKey = httpMethod + REQUEST_PATH_SEPARATOR + path.replaceFirst(EnvUtil.getContextPath(), "");
        return methods.get(urlKey);
    }

    public void initClassMethod(String packageName) {
        Reflections reflections = new Reflections(packageName);
        Set<Class<?>> classes = reflections.getTypesAnnotatedWith(RequestMapping.class);
        for (Class<?> aClass : classes) {
            initClassMethod(aClass);
        }
    }

    private void initClassMethod(Class<?> clazz) {
        RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
        for (String classPath : requestMapping.value()) {
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(RequestMapping.class)) {
                    parseSubAnnotations(method, classPath);
                    continue;
                }
                requestMapping = method.getAnnotation(RequestMapping.class);
                RequestMethod[] requestMethods = requestMapping.method();
                if (requestMethods.length == 0) {
                    requestMethods = RequestMethod.values();
                }
                for (String methodPath : requestMapping.value()) {
                    for (RequestMethod requestMethod : requestMethods) {
                        String urlKey = requestMethod.name() + REQUEST_PATH_SEPARATOR + classPath + methodPath;
                        addUrlAndMethodRelation(urlKey, method);
                    }
                }
            }
        }


    }

    private void parseSubAnnotations(Method method, String classPath) {

        final GetMapping getMapping = method.getAnnotation(GetMapping.class);
        final PostMapping postMapping = method.getAnnotation(PostMapping.class);
        final PutMapping putMapping = method.getAnnotation(PutMapping.class);
        final DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
        final PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);

        if (getMapping != null) {
            put(RequestMethod.GET, classPath, getMapping.value(), getMapping.params(), method);
        }

        if (postMapping != null) {
            put(RequestMethod.POST, classPath, postMapping.value(), postMapping.params(), method);
        }

        if (putMapping != null) {
            put(RequestMethod.PUT, classPath, putMapping.value(), putMapping.params(), method);
        }

        if (deleteMapping != null) {
            put(RequestMethod.DELETE, classPath, deleteMapping.value(), deleteMapping.params(), method);
        }

        if (patchMapping != null) {
            put(RequestMethod.PATCH, classPath, patchMapping.value(), patchMapping.params(), method);
        }
    }

    private void put(RequestMethod requestMethod, String classPath, String[] requestPaths, String[] requestParams,
                     Method method) {
        if (ArrayUtils.isEmpty(requestPaths)) {
            String urlKey = requestMethod.name() + REQUEST_PATH_SEPARATOR + classPath;
            addUrlAndMethodRelation(urlKey, method);
            return;
        }
        for (String requestPath : requestPaths) {
            String urlKey = requestMethod.name() + REQUEST_PATH_SEPARATOR + classPath + requestPath;
            addUrlAndMethodRelation(urlKey, method);
        }
    }

    private void addUrlAndMethodRelation(String urlKey, Method method) {
        methods.put(urlKey, method);
    }

    private String getPath(HttpServletRequest request) {
        try {
            return new URI(request.getRequestURI()).getPath();
        } catch (URISyntaxException e) {
            throw new NacosRuntimeException(NacosException.NOT_FOUND, "Invalid URI");
        }
    }
}
